{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 2: Federated Learning入門\n",
    "\n",
    "前回のセクションでは、PointerTensorについて学びました。PointerTensorは、プライバシーに配慮したディープラーニングを実現するための基礎となるツールで、インフラとしての役割をはたします。このセクションでは、それらのツールの使い方、特に私たちが最初に学ぶプライバシーに配慮したディープラーニングの手法である、Federated Learningの実装方法について学びます。\n",
    "\n",
    "著者:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "\n",
    "### Federated Learningとは?\n",
    "\n",
    "Federated Learningはディープラーニングのモデルを学習させるシンプル、かつパワフルな手法の一つです。\n",
    "\n",
    "ディープラーニングの学習プロセスは、通常、データを集めるところから始まります。\n",
    "データは人がパソコンやモバイルなどの端末を使って記録、生成することによって作成され、実際に学習プロセスを実行するマシンに集められます。\n",
    "その後、集められたデータを使って学習処理を実行します。\n",
    "これが、一般的なディープラーニングの学習プロセスです。\n",
    "\n",
    "ですが、Federated Learningではこのプロセスをひっくり返します。\n",
    "\n",
    "データをモデルのところへ持って行くのではなく、モデルをデータのあるところへ持って行くのです。\n",
    "データの所有者はデータのコピーを誰にも渡さず、アクセス権限だけをコントロールするという考え方です。\n",
    "\n",
    "クールでしょ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Section 2.1 - Federated Learningのトイモデル（初歩的な事例）\n",
    "\n",
    "まずは、みなさんも良くご存知の（ごく普通の？）、データを一箇所に集めるやり方でのモデル学習を確認しましょう。\n",
    "最小構成で以下の3つが必要です。\n",
    "\n",
    "- トイ データセット\n",
    "- モデル\n",
    "- 基本的なトレーニングロジック\n",
    "\n",
    "注記: もし、トイモデルを学習するPytorchのAPIについて解説が必要な方は、チュートリアルを進める前に[fast.ai](http://fast.ai)のコースを学習される事をおすすめします。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# トイデータセット（初歩的なモデル学習で使うデータ）\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# トイモデル（初歩的なモデル）\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "def train():\n",
    "    # Training Logic\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(20):\n",
    "\n",
    "        # 1) Gradientsの初期化 (もし、あれば)\n",
    "        opt.zero_grad()\n",
    "\n",
    "        # 2) 予測を行う\n",
    "        pred = model(data)\n",
    "\n",
    "        # 3) ロスの計算\n",
    "        loss = ((pred - target)**2).sum()\n",
    "\n",
    "        # 4) 勾配ベクトルの計算\n",
    "        loss.backward()\n",
    "\n",
    "        # 5) 勾配ベクトルの適用\n",
    "        opt.step()\n",
    "\n",
    "        # 6) 進捗を表示\n",
    "        print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "できました。今回は基本的なモデルを（従来からある）ごくごく一般的な手法で学習させてみました。全ての学習データはわたしたちのマシン上にあります。ローカルにあるデータセットを使ってローカルにあるモデルのパラメータをアップデートしています。しかし、Federatd Learningの世界では同じようにはいきません。\n",
    "では、Federated Learning\"的\"な方法で同じモデルを学習させてみましょう。\n",
    "\n",
    "必要なステップ:\n",
    "\n",
    "- 複数のワーカー（ユーザーの事です）を作成する\n",
    "- 作成したワーカーが保有するPointerTensorを取得する\n",
    "- Federated Learning用にトレーニングロジックを調整する\n",
    "\n",
    "    新しいトレーニングステップ（トレーニングロジック）:\n",
    "    - モデルをデータセットを保持しているワーカーへ送る\n",
    "    - モデルをデータセットのある場所で学習させる\n",
    "    - モデルを返してもらい、次のワーカーに対して同様の処理を行う。ワーカーの数だけ繰り返す。\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# データセットを保持する複数のワーカーを作成する\n",
    "\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# トイデータセット（初歩的なモデルで使うデータセット）\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# 学習データを分割し、半分をBob、残り半分をAliceに送り、それぞれのポインタを取得する\n",
    "data_bob = data[0:2]\n",
    "target_bob = target[0:2]\n",
    "\n",
    "data_alice = data[2:]\n",
    "target_alice = target[2:]\n",
    "\n",
    "# トイモデル（初歩的なモデル）を初期化する\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "data_bob = data_bob.send(bob)\n",
    "data_alice = data_alice.send(alice)\n",
    "target_bob = target_bob.send(bob)\n",
    "target_alice = target_alice.send(alice)\n",
    "\n",
    "# BobとAliceのポインタをそれぞれ纏める\n",
    "datasets = [(data_bob,target_bob),(data_alice,target_alice)]\n",
    "\n",
    "opt = optim.SGD(params=model.parameters(),lr=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train():\n",
    "    # トレーニングロジック\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(10):\n",
    "        \n",
    "        # NEW) ワーカーの数だけ繰り返す\n",
    "        for data,target in datasets:\n",
    "            \n",
    "            # NEW) モデルを該当ワーカーへ送る\n",
    "            model.send(data.location)\n",
    "\n",
    "            # 1) 勾配ベクトルの初期化 (もし、あれば)\n",
    "            opt.zero_grad()\n",
    "\n",
    "            # 2) 予測する\n",
    "            pred = model(data)\n",
    "\n",
    "            # 3) ロスを計算する\n",
    "            loss = ((pred - target)**2).sum()\n",
    "\n",
    "            # 4) 勾配ベクトルを計算する\n",
    "            loss.backward()\n",
    "\n",
    "            # 5) 勾配ベクトルの適用\n",
    "            opt.step()\n",
    "            \n",
    "            # NEW) モデルを返してもらう (計算済み勾配ベクトル含む)\n",
    "            model.get()\n",
    "\n",
    "            # 6) 進捗を表示\n",
    "            print(loss.get()) # NEW) 微調整... .get()でデータを私たちのマシンに持ってくる必要があります。\n",
    "    \n",
    "# federated learning環境下での勾配ベクトルの集計作業 ＜ 後ほど解説します"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 良く出来ました！\n",
    "\n",
    "今回は、トイモデル（初歩的なモデル）をFederated Learningの手法を使って学習してみました。モデルをそれぞれのワーカーへ送り、そこで勾配ベクトルの計算とモデルへの適用を行い、その後、学習済みモデルを私たちのマシンへ戻しています。この一連のプロセスにおいて、私たちは学習データ自体に対するアクセス権限をBobやAliceに要求していません。つまり、BobとAliceのプライバシーは保たれています！\n",
    "\n",
    "\n",
    "## この例の不完全な部分\n",
    "\n",
    "この例はFederated Learningの入門事例としては良いものの、いくつかの点で問題があります。その中でも最もよく知られた問題は、私たちが`model.get()`によって返してもらう学習済みのモデルの勾配ベクトルを調べる事で、実際にはBobやAliceのデータについて多くを知る事が可能だと言う事です。特定の状況下では、彼らのデータを完璧に復元することも可能です。\n",
    "\n",
    "では、どうすれば良いでしょうか？まず思いつく手法は、私たちのマシンにモデルを返してもらう前に、複数のワーカーのgradientsを平均することです。しかし、そのためにはPointerTensorオブジェクトの高度な使い方を理解する必要があります。次のセクションでは、PointerTensorの発展的な機能について学び、今回学んだシンプルな例をアップデートしていきましょう。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# おめでとうございます！コミュニティに入ろう！\n",
    "\n",
    "本チュートリアルを完了しました。おめでとうございます！もし、このチュートリアルを気に入って、プライバシーに配慮した非中央集権的なAI技術や付随する（データやモデルの）サプライチェーンにご興味があって、プロジェクトに参加したいと思われるなら、以下の方法で可能です。\n",
    "\n",
    "### PySyftのGitHubレポジトリにスターをつける\n",
    "\n",
    "一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Slackに入る\n",
    "\n",
    "最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。\n",
    "下記フォームから入る事ができます。\n",
    "[http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### コードプロジェクトに参加する\n",
    "\n",
    "コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、\"Projects\"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、\"good first issue\"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### 寄付\n",
    "\n",
    "もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
